package com.powernode.ssm.config;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;


import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.*;

@Aspect
@Component
//@Profile("!prod") //仅在非 prod 环境（如 dev/test）生效
@ConditionalOnProperty(prefix = "myLog.aroundLog", name = "enable", havingValue = "true")
public class LoggingAspect {

    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);

    // 时间格式
    private static final DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    // JSON 处理器
    private static final ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 切点：拦截 com.example.controller 包下的所有 public 方法
     * 请替换为你的实际 Controller 包路径，如 com.myapp.controller
     */
    @Around("execution(public * com.powernode.ssm.controller..*.*(..))")
    public Object logControllerMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        // === 1. 生成或获取 traceId，并放入 MDC ===
        String traceId = UUID.randomUUID().toString(); // 你可以替换为从请求头获取，如 X-B3-TraceId
        MDC.put("traceId", traceId);

        // === 2. 记录开始时间 ===
        Instant startTime = Instant.now();

        // === 3. 获取请求信息 ===
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        String requestUrl = "N/A";
        String httpMethod = "N/A";
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            requestUrl = request.getRequestURL().toString();
            httpMethod = request.getMethod();

            // === 4. 打印 HTTP 请求头信息 ===
            logger.info("======= HTTP 请求头 =======");
            Enumeration<String> headerNames = request.getHeaderNames();
            while (headerNames.hasMoreElements()) {
                String headerName = headerNames.nextElement();
                String headerValue = request.getHeader(headerName);
                logger.info("请求头 - {} : {}", headerName, headerValue);
            }
            logger.info("======= HTTP 请求头 =======");
        }

        // === 5. 获取方法信息 ===
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = signature.getMethod().getName();

        // === 6. 收集方法参数名和值，转为 JSON 打印 ===
        String[] parameterNames = signature.getParameterNames();
        Object[] parameterValues = joinPoint.getArgs();
        // 构建方法参数名与值的 Map，用于 JSON 打印，排除 HttpServletRequest、HttpServletResponse、MultipartFile
        Map<String, Object> paramsMap = new LinkedHashMap<>();
        if (parameterNames != null && parameterValues != null && parameterNames.length == parameterValues.length) {
            for (int i = 0; i < parameterNames.length; i++) {
                String name = parameterNames[i];
                Object value = parameterValues[i];

                if (!(value instanceof javax.servlet.http.HttpServletRequest) &&
                        !(value instanceof javax.servlet.http.HttpServletResponse)) {
                    // ✅ 替换原来直接放入 value 的逻辑，改为递归处理字段
                    Object processedValue = processMultipartFields(value);
                    paramsMap.put(name, processedValue);
                } else {
                    logger.debug("【AOP日志】跳过序列化参数 [{}] : {} (类型: {})", name, value, value.getClass().getSimpleName());
                }

            }
        }
        String paramsJson = "无";
        if (!paramsMap.isEmpty()) {
            try {
                paramsJson = objectMapper.writeValueAsString(paramsMap);
            } catch (JsonProcessingException e) {
                paramsJson = "[参数JSON格式化失败]";
                logger.warn("参数转JSON异常", e);
            }
        }

        // === 7. 打印请求日志（带 traceId、时间、参数 JSON）===
        logger.info("======= Controller 请求开始 =======");
        logger.info("traceId         : {}", traceId);
        logger.info("请求URL         : {}", requestUrl);
        logger.info("HTTP方法        : {}", httpMethod);
        logger.info("Controller类    : {}", className);
        logger.info("方法名          : {}", methodName);
        logger.info("请求参数(JSON)  : {}", paramsJson);

        LocalDateTime startLocalDateTime = LocalDateTime.ofInstant(startTime, ZoneId.systemDefault());
        String startTimeFormatted = startLocalDateTime.format(DTF);
        logger.info("开始时间        : {}", startTimeFormatted);

        logger.info("======= 进入方法 =======");

        Object result = null;
        try {
            // === 8. 执行目标方法 ===
            result = joinPoint.proceed();

            // === 9. 打印响应日志 ===
            Instant endTime = Instant.now();
            long durationMs = Duration.between(startTime, endTime).toMillis();

            LocalDateTime endLocalDateTime = LocalDateTime.ofInstant(endTime, ZoneId.systemDefault());
            String endTimeFormatted = endLocalDateTime.format(DTF);
            String durationFormatted = durationMs + " ms";

            String resultJson = "无";
            if (result != null) {
                try {
                    resultJson = objectMapper.writeValueAsString(result);
                } catch (JsonProcessingException e) {
                    resultJson = "[返回值JSON格式化失败]";
                    logger.warn("返回值转JSON异常", e);
                }
            }

            logger.info("======= Controller 响应返回 =======");
            logger.info("traceId         : {}", traceId);
            logger.info("响应结果(JSON)  : {}", resultJson);
            logger.info("耗时            : {}", durationFormatted);
            logger.info("结束时间        : {}", endTimeFormatted);

            logger.info("======= 请求结束 =======");

            return result;
        } catch (Throwable e) {
            // 异常处理
            Instant endTime = Instant.now();
            long durationMs = Duration.between(startTime, endTime).toMillis();

            LocalDateTime endLocalDateTime = LocalDateTime.ofInstant(endTime, ZoneId.systemDefault());
            String endTimeFormatted = endLocalDateTime.format(DTF);

            logger.error("======= Controller 发生异常 =======");
            logger.error("traceId         : {}", traceId);
            logger.error("异常信息        : {}", e.getMessage(), e);
            logger.error("耗时            : {} ms", durationMs);
            logger.error("结束时间        : {}", endTimeFormatted);

            logger.error("======= 请求异常结束 =======");

            throw e;
        } finally {
            // === 10. 清理 MDC，防止内存泄漏或污染其他请求 ===
            MDC.clear();
        }
    }

    /**
     * 递归处理对象中的字段，如果发现 MultipartFile，提取文件信息；否则尝试直接使用对象值
     */
    public static Object processMultipartFields(Object obj) {
        if (obj == null) {
            return null;
        }

        Class<?> clazz = obj.getClass();

        // 如果是 MultipartFile 类型
        if (MultipartFile.class.isAssignableFrom(clazz)) {
            MultipartFile file = (MultipartFile) obj;
            Map<String, Object> fileInfo = new LinkedHashMap<>();
            fileInfo.put("originalFilename", file.getOriginalFilename());
            fileInfo.put("size", file.getSize());
            fileInfo.put("contentType", file.getContentType());
            return fileInfo;
        }

        // 如果是简单类型，直接返回
        if (isSimpleType(clazz)) {
            return obj;
        }

        // 自定义对象类型，递归处理字段
        Map<String, Object> fieldMap = new LinkedHashMap<>();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            try {
                Object fieldValue = field.get(obj);
                String fieldName = field.getName();
                Object processedValue = processMultipartFields(fieldValue);
                fieldMap.put(fieldName, processedValue);
            } catch (IllegalAccessException e) {
                logger.warn("无法访问字段 [{}] 的值，类型：{}", field.getName(), field.getType().getSimpleName(), e);
                fieldMap.put(field.getName(), "[无法访问字段值]");
            }
        }
        return fieldMap;
    }

    /**
     * 判断是否为简单类型（无需递归处理的类型）
     */
    private static boolean isSimpleType(Class<?> clazz) {
        return clazz.isPrimitive() ||
                clazz == String.class ||
                Number.class.isAssignableFrom(clazz) ||
                clazz == Boolean.class ||
                clazz == Character.class ||
                clazz.isEnum() ||
                clazz == LocalDateTime.class ||
                clazz == LocalDate.class ||
                clazz == Date.class;
    }
}